﻿using ExcelParser.CodeGen;
using ExcelParser.Reader;
using System.Collections.Generic;

namespace ExcelParser.Writer
{
    public class CSharpWriter : CodeWriter
    {
        /// <summary>
        /// 写头
        /// </summary>
        public override void WriteHeader()
        {
            base.WriteHeader();
            WriteLine("using System.Collections.Generic;");
            WriteLine("using System.Runtime.Serialization.Formatters.Binary;");
            WriteLine("using System.IO;");
            WriteLine("using System;");
            WriteLine();
        }

        /// <summary>
        /// 写注释
        /// </summary>
        /// <param name="desc"></param>
        public override void WriteComment(string desc)
        {
            base.WriteComment(desc);
            WriteLine("/// <summary>");
            WriteLine("/// {0}", desc);
            WriteLine("/// </summary>");
        }

        /// <summary>
        /// 生成代码
        /// </summary>
        /// <param name="name"></param>
        /// <param name="desc"></param>
        /// <param name="fields"></param>
        /// <returns></returns>
        public override string GenCode(ExcelSheet sheet, string name, ICollection<ExcelField> fields)
        {
            WriteHeader();
            BeginWriteNamespace(sheet.nspace);
            WriteLine("public class {0}", name);
            WriteLine("{{");
            Indent++;
            GenClass(name, fields);
            GenRecord(name, fields);
            GenData(sheet, name, fields);
            Indent--;
            WriteLine("}}");
            EndWriteNamespace(sheet.nspace);
            return m_builder.ToString();
        }

        private void BeginWriteNamespace(string nspace)
        {
            if (string.IsNullOrEmpty(nspace))
                return;

            WriteLine("namespace {0}", nspace);
            WriteLine("{{");
            Indent++;
        }

        private void EndWriteNamespace(string nspace)
        {
            if (string.IsNullOrEmpty(nspace))
                return;

            Indent--;
            WriteLine("}}");
        }

        /// <summary>
        /// 生成类型
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fields"></param>
        private void GenClass(string name, ICollection<ExcelField> fields)
        {
            foreach (var field in fields)
            {
                field.GenClass(this);
            }
            WriteLine();
        }

        /// <summary>
        /// 生成记录
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fields"></param>
        private void GenRecord(string name, ICollection<ExcelField> fields)
        {
            WriteLine("[Serializable]");
            WriteLine("public class Record");
            WriteLine("{{");
            Indent++;
            foreach (var field in fields)
            {
                field.GenCode(this);
            }
            Indent--;
            WriteLine("}}");
            WriteLine();
        }

        /// <summary>
        /// 生成数据
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fields"></param>
        private void GenData(ExcelSheet sheet, string name, ICollection<ExcelField> fields)
        {
            WriteLine("private static List<Record> s_datas = new List<Record>();\n");
            WriteComment("添加数据");
            WriteLine("public static void AddData(Record data)");
            WriteLine("{{");
            Indent++;
            WriteLine("if (data == null)");
            Indent++;
            WriteLine("return;\n");
            Indent--;
            WriteLine("s_datas.Add(data);");
            Indent--;
            WriteLine("}}");
            WriteLine();

            //find data
            WriteComment("查找数据");
            WriteLine("public static Record FindConfigByIndex(int index)");
            WriteLine("{{");
            Indent++;
            WriteLine("return s_datas[index];");
            Indent--;
            WriteLine("}}");
            WriteLine();

            List<string> initPrimaryMethods = new List<string>();
            //primary key
            for (int i = 0; i < sheet.primaryKeys.Count; i++)
            {
                string primarykeyString = sheet.primaryKeys[i];

                if (string.IsNullOrEmpty(primarykeyString))
                    continue;

                string[] primarykeys = primarykeyString.Split(new char[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries);
                if (primarykeys.Length > 3)
                {
                    ConfigParser.ErrorLogger?.Invoke("联合主键最多支持3个");
                    continue;
                }

                bool unfind = false;
                ExcelField[] primaryFields = new ExcelField[primarykeys.Length];
                for (int primaryIndex = 0; primaryIndex < primarykeys.Length; primaryIndex++)
                {
                    ExcelField field = sheet.config.FindField(primarykeys[primaryIndex]);
                    if (field == null)
                    {
                        ConfigParser.ErrorLogger?.Invoke(string.Format("找不到主键:{0}.{1}", name, primarykeyString));
                        unfind = true;
                        break;
                    }

                    primaryFields[primaryIndex] = field;
                }
                if (unfind)
                    continue;

                string primaryDictType = GetPrimaryDictType(primaryFields);
                string primaryDictName = GetPrimaryDictName(primaryFields);
                WriteLine("private static {0} {1} = new {0}();", primaryDictType, primaryDictName);

                //init
                initPrimaryMethods.Add(primaryDictName);
                WriteLine("private static void InitConfigBy{0}()", primaryDictName);
                WriteLine("{{");
                Indent++;
                WriteLine("for (int i = 0; i < s_datas.Count; i++)");
                WriteLine("{{");
                Indent++;
                WriteLine("Record record = s_datas[i];");

                string dictType = null;
                string dictName = null;
                string parentDictName = null;
                for (int primaryIndex = 0; primaryIndex < primaryFields.Length - 1; primaryIndex++)
                {
                    dictType = GetPrimaryDictType(primaryFields, primaryIndex + 1);
                    dictName = GetPrimaryDictName(primaryFields, primaryIndex + 1);
                    parentDictName = GetPrimaryDictName(primaryFields, primaryIndex);

                    WriteLine("{0} {1} = null;", dictType, dictName);
                    WriteLine("if(!{0}.TryGetValue(record.{1},out {2}))", parentDictName, primaryFields[primaryIndex].Name, dictName);
                    WriteLine("{{");
                    Indent++;
                    WriteLine("{0} = new {1}();", dictName, dictType);
                    WriteLine("{0}.Add(record.{1},{2});", parentDictName, primaryFields[primaryIndex].Name, dictName);
                    Indent--;
                    WriteLine("}}");
                }

                string addElementString = primaryDictName;
                for (int primaryIndex = 0; primaryIndex < primaryFields.Length - 1; primaryIndex++)
                {
                    addElementString += "[record." + primaryFields[primaryIndex].Name + "]";
                }
                addElementString += ".Add(record." + primaryFields[primaryFields.Length - 1].Name + ",i);";
                WriteLine(addElementString);
                Indent--;
                WriteLine("}}");
                Indent--;
                WriteLine("}}");

                //find
                string findMethodName = "public static Record FindConfigBy" + primaryDictName + "(";
                for (int primaryIndex = 0; primaryIndex < primaryFields.Length; primaryIndex++)
                {
                    ExcelField field = primaryFields[primaryIndex];
                    if (primaryIndex != 0)
                    {
                        findMethodName += ",";
                    }
                    findMethodName += field.Type + " " + field.Name + "param";
                }
                findMethodName += ")";
                WriteLine(findMethodName);
                WriteLine("{{");
                Indent++;

                for (int primaryIndex = 0; primaryIndex < primaryFields.Length - 1; primaryIndex++)
                {
                    dictType = GetPrimaryDictType(primaryFields, primaryIndex + 1);
                    dictName = GetPrimaryDictName(primaryFields, primaryIndex + 1);
                    parentDictName = GetPrimaryDictName(primaryFields, primaryIndex);

                    WriteLine("{0} {1} = null;", dictType, dictName);
                    WriteLine("if(!{0}.TryGetValue({1}param,out {2}))", parentDictName, primaryFields[primaryIndex].Name, dictName);
                    WriteLine("{{");
                    Indent++;
                    WriteLine("return null;");
                    Indent--;
                    WriteLine("}}");
                }

                if (primaryFields.Length == 1)
                {
                    dictName = primaryDictName;
                }

                WriteLine("int index;");
                WriteLine("if(!{0}.TryGetValue({1}param,out index))", dictName, primaryFields[primaryFields.Length - 1].Name);
                WriteLine("{{");
                Indent++;
                WriteLine("return null;");
                Indent--;
                WriteLine("}}");

                WriteLine("return s_datas[index];");

                Indent--;
                WriteLine("}}");
                WriteLine();
            }

            //serialize
            WriteComment("序列化");
            WriteLine("public static void Serialize(string path)");
            WriteLine("{{");
            Indent++;
            BeginTryCatch();
            //create directory
            WriteLine("string directory = Path.GetDirectoryName(path);");
            WriteLine("if (!Directory.Exists(directory))");
            WriteLine("{{");
            Indent++;
            WriteLine("Directory.CreateDirectory(directory);");
            Indent--;
            WriteLine("}}");
            WriteLine("using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))");
            WriteLine("{{");
            Indent++;
            WriteLine("BinaryFormatter formatter = new BinaryFormatter();");
            WriteLine("formatter.Serialize(fs, s_datas);");
            Indent--;
            WriteLine("}}");
            EndTryCatch(false);
            Indent--;
            WriteLine("}}");

            //deserialize
            WriteComment("反序列化");
            WriteLine("public static void Deserialize(byte[] buffer)");
            WriteLine("{{");
            Indent++;
            BeginTryCatch();
            WriteLine("using (MemoryStream ms = new MemoryStream(buffer))");
            WriteLine("{{");
            Indent++;
            WriteLine("BinaryFormatter formatter = new BinaryFormatter();");
            WriteLine("s_datas = formatter.Deserialize(ms) as List<Record>;");
            for (int i = 0; i < initPrimaryMethods.Count; i++)
            {
                WriteLine("InitConfigBy{0}();", initPrimaryMethods[i]);
            }
            Indent--;
            WriteLine("}}");
            EndTryCatch(true);
            Indent--;
            WriteLine("}}");

            //clear
            WriteComment("清除数据");
            WriteLine("public static void Clear()");
            WriteLine("{{");
            Indent++;
            WriteLine("s_datas.Clear();");
            for (int i = 0; i < initPrimaryMethods.Count; i++)
            {
                WriteLine("{0}.Clear();", initPrimaryMethods[i]);
            }
            Indent--;
            WriteLine("}}");
        }

        private string GetPrimaryDictType(ExcelField[] fields, int startIndex = 0)
        {
            if (fields.Length <= startIndex)
                return null;

            string primaryDictType = "Dictionary<" + fields[startIndex].Type;
            for (int i = startIndex + 1; i < fields.Length; i++)
            {
                primaryDictType += ",Dictionary<" + fields[i].Type;
            }
            primaryDictType += ",int>";
            for (int primaryIndex = startIndex + 1; primaryIndex < fields.Length; primaryIndex++)
            {
                primaryDictType += ">";
            }
            return primaryDictType;
        }

        private string GetPrimaryDictName(ExcelField[] fields, int startIndex = 0)
        {
            string primaryKeyName = string.Empty;
            for (int primaryIndex = startIndex; primaryIndex < fields.Length; primaryIndex++)
            {
                primaryKeyName += fields[primaryIndex].Name;
            }
            return primaryKeyName;
        }


        private void BeginTryCatch()
        {
            WriteLine("try");
            WriteLine("{{");
            Indent++;
        }

        private void EndTryCatch(bool throwException)
        {
            Indent--;
            WriteLine("}}");
            WriteLine("catch (Exception e)");
            WriteLine("{{");
            Indent++;
            WriteLine("UnityEngine.Debug.Log(e.Message);");
            if (throwException)
            {
                WriteLine("throw;");
            }
            Indent--;
            WriteLine("}}");
        }

        public override string ConvertType(string type)
        {
            return type;
        }

        public override string ConvertFullType(string type)
        {
            switch (type)
            {
                case "byte":
                    return "System.Byte";
                case "short":
                    return "System.Int16";
                case "int":
                    return "System.Int32";
                case "long":
                    return "System.Int64";
                case "float":
                    return "System.Single";
                case "double":
                    return "System.Double";
                case "bool":
                    return "System.Boolean";
                case "string":
                    return "System.String";
                default:
                    return string.Empty;
            }
        }

        public override string GenUnityConfigManager(List<ExcelSheet> sheets, string nspace)
        {
            WriteLine("using System.Collections;");
            WriteLine("using UnityEngine;");
            WriteLine();
            BeginWriteNamespace(nspace);
            WriteLine("public static class ConfigManager");
            WriteLine("{{");
            Indent++;
            WriteLine("public static IEnumerator LoadAllConfigs(string path)");
            WriteLine("{{");
            Indent++;
            WriteLine("WWW www;");
            for (int i = 0; i < sheets.Count; i++)
            {
                WriteLine("www = new WWW(path + \"/{0}.bytes\");", sheets[i].name);
                WriteLine("yield return www;");
                WriteLine(sheets[i].name + ".Deserialize(www.bytes);");
                WriteLine();
            }
            Indent--;
            WriteLine("}}");
            Indent--;
            WriteLine("}}");
            EndWriteNamespace(nspace);

            return m_builder.ToString();
        }
    }
}